/*******************************************************************************
 * SDP Project AUT 2012
 * 
 *                ***********Driving Assistant************
 * Team:
 * Gisela, Deidre, Brijesh, Robert
 * 
 * This class finds the available serial ports on the OS. Uses the serial 
 * port that is connected to the arduino and receives the output from 
 * the arduino.
 * 
 * @version 1.0
 * 
 * Copyright (c) 2012 by DrivingAssistant Team, Inc., All Rights Reserved.
 * 
 ******************************************************************************/

package com.drivingAssistant.model;

import com.drivingAssistant.gui.DrivingAssistantGUI;
import gnu.io.*;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.TooManyListenersException;
import javax.sound.sampled.LineUnavailableException;

/**
 * ArduinoCommunicator class
 * 
 */
public class ArduinoCommunicator implements SerialPortEventListener {

    // constants
    final static int TIMEOUT = 2000;
    final static int SPACE_ASCII = 32;
    final static int DASH_ASCII = 45;
    final static int NEW_LINE_ASCII = 10;
    final static int LEVEL_ONE = 4;
    final static int LEVEL_TWO = 1;
    final static int LEVEL_THREE = 0;
    final static int GAP = 10;
    
    // variables
    DrivingAssistantGUI window = null;
    ExceptionLogger exLog = null;
    SoundOff so = null;
    Double currentDistance = 0.0;
    String Level1 = "";
    String Level2 = "";
    String Level3 = "";
    String temp = "";
    private Enumeration ports = null;
    private HashMap portMap = new HashMap();
    private CommPortIdentifier selectedPortIdentifier = null;
    private SerialPort serialPort = null;
    private InputStream input = null;
    private OutputStream output = null;
    private boolean bConnected = false;
    String inputText = "";
    String logText = "";
    int level1range = 0, level2range = 0, level3range = 0, userRange = 0;

    /**
     * constructor for class to initialize class objects
     * 
     * @param window which is an instance of the UI
     */
    public ArduinoCommunicator(DrivingAssistantGUI window)
            throws LineUnavailableException {
        this.window = window;
        this.window.beep1.setSelected(true); // default sound
        so = new SoundOff();
        exLog = new ExceptionLogger();
    }

    /**
     * Find the ports on current OS
     */
    public void searchForPorts() {
        ports = CommPortIdentifier.getPortIdentifiers();

        while (ports.hasMoreElements()) {
            CommPortIdentifier currentPort = (CommPortIdentifier) ports.nextElement();
            if (currentPort.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                window.comBox.addItem(currentPort.getName());
                portMap.put(currentPort.getName(), currentPort);
            }
        }
    }

    /**
     * Connect to the selected port from the combo box
     */
    public void connect() {
        String selectedPort = (String) window.comBox.getSelectedItem();
        selectedPortIdentifier = (CommPortIdentifier) portMap.get(selectedPort);

        CommPort commPort = null;

        try {
            commPort = selectedPortIdentifier.open("ControlPanel", TIMEOUT);
            serialPort = (SerialPort) commPort;
            setConnected(true);

            //logging
            logText = selectedPort + " opened successfully.";
            exLog.addLog(logText);
            window.distanceTextArea.setForeground(Color.black);
            window.distanceTextArea.append(logText + "\n");
            
        } catch (PortInUseException e) {
            logText = selectedPort + " is in use. (" + e.toString() + ")";
            exLog.addLog(logText);
            window.distanceTextArea.setForeground(Color.RED);
            window.distanceTextArea.append(inputText + "\n");
        } catch (Exception e) {
            logText = "Failed to open " + selectedPort + "(" + e.toString() + ")";
            exLog.addLog(logText);
            window.distanceTextArea.append(inputText + "\n");
            window.distanceTextArea.setForeground(Color.RED);
        }
    }

    /**
     * Initialize the input and output streams
     * 
     * @return true if succeeded
     */
    public boolean initIOStream() {
        boolean successful = false;

        try {
            input = serialPort.getInputStream();
            output = serialPort.getOutputStream();
            successful = true;
            return successful;
        } catch (IOException e) {
            logText = "I/O Streams failed to open. (" + e.toString() + ")";
            exLog.addLog(logText);
            window.distanceTextArea.setForeground(Color.red);
            window.distanceTextArea.append(inputText + "\n");
            return successful;
        }
    }

    /**
     * Start event Listener to start reading data when data is being ported.
     */
    public void initListener() {
        try {
            serialPort.addEventListener(this);
            serialPort.notifyOnDataAvailable(true);
        } catch (TooManyListenersException e) {
            logText = "Too many listeners. (" + e.toString() + ")";
            exLog.addLog(logText);
            window.distanceTextArea.setForeground(Color.red);
            window.distanceTextArea.append(inputText + "\n");
        }
    }

    /**
     * Take care of connections
     */
    public void disconnect() {
        try {
            if (serialPort != null) {
                serialPort.removeEventListener();
                serialPort.close();
            }
            if (input != null && output != null) {
                input.close();
                output.close();
            } 
            setConnected(false);
            inputText = "Disconnected.";
            window.distanceTextArea.setForeground(Color.red);
            window.distanceTextArea.append(inputText + "\n");
        } catch (Exception e) {
            logText = "Failed to close " + serialPort.getName() + "(" + e.toString() + ")";
            exLog.addLog(logText);
            window.distanceTextArea.setForeground(Color.red);
            window.distanceTextArea.append(inputText + "\n");
        }
    }

    /**
     * Get connection success
     * 
     * @return connection
     */
    final public boolean getConnected() {
        return bConnected;
    }

    /**
     * Set the connection status
     * 
     * @param bConnected 
     */
    public void setConnected(boolean bConnected) {
        this.bConnected = bConnected;
    }

    /**
     * Process data when it is received
     * 
     * @param evt 
     */
    @Override
    public synchronized void serialEvent(SerialPortEvent evt) {
        if (evt.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
            try {
                byte singleData = (byte) input.read();
                if (singleData != NEW_LINE_ASCII) {
                    inputText = new String(new byte[]{singleData});
                    window.distanceTextArea.append(inputText);
                    Level1 += inputText;
                } else {
                    temp = Level1;
                    window.distanceTextArea.append("\n");
                    Level1 = ""; // reset reading
                    currentDistance = Double.parseDouble(temp);
                    soundAlarm(currentDistance);
                }
            } catch (Exception e) {
                logText = "Failed to read data. (" + e.toString() + ")";
                exLog.addLog(logText);
            }
        }
    }

    /**
     * 
     * @param distance 
     */
    public synchronized void soundAlarm(double distance) {
        userRange = window.getInitialValue();
        try {
            so.setClip(window.getSelectedSound());
            if (distance <= userRange) {
                so.playClip(LEVEL_ONE);
            } else if (distance > userRange && distance <= userRange + GAP) {
                so.playClip(LEVEL_TWO);
            } else if (distance > userRange + GAP && distance 
                    <= userRange + GAP + GAP) {
                so.playClip(LEVEL_THREE);
            } else if (distance > userRange + GAP + GAP) {
                so.stop();
            }
        } catch (LineUnavailableException ex) {
            logText += "Failed to capture reading accurately..." + ex.toString();
            exLog.addLog(logText);
        }
    }
    
}
